<script setup lang="ts">
import {ref, computed} from 'vue'
import type {CSSProperties} from 'vue'

export interface MenuItemType {
  key: string
  label: string
  icon?: string
  disabled?: boolean
  showLine?: boolean
  children?: MenuItemType[]

  [key: string]: any
}

interface Props {
  menuItems: MenuItemType[]
  /** 菜单宽度 */
  menuWidth?: number
  /** 子菜单宽度 */
  submenuWidth?: number
  /** 菜单项高度 */
  itemHeight?: number
  /** 边界距离 */
  boundaryDistance?: number
  /** 菜单内边距 */
  menuPadding?: number
  /** 菜单项水平内边距 */
  itemPaddingX?: number
  /** 菜单圆角 */
  borderRadius?: number
  /** 动画持续时间 */
  animationDuration?: number
}

const props = withDefaults(defineProps<Props>(), {
  menuWidth: 120,
  submenuWidth: 150,
  itemHeight: 32,
  boundaryDistance: 10,
  menuPadding: 5,
  itemPaddingX: 6,
  borderRadius: 6,
  animationDuration: 100
})

const emit = defineEmits<{
  (e: 'select', item: MenuItemType): void
  (e: 'show'): void
  (e: 'hide'): void
}>()

const visible = ref(false)
const position = ref({x: 0, y: 0})

// 计算菜单样式
const menuStyle = computed(
  (): CSSProperties => ({
    position: 'fixed' as const,
    left: `${position.value.x}px`,
    top: `${position.value.y}px`,
    zIndex: 2000,
    width: `${props.menuWidth}px`
  })
)

// 计算菜单列表样式
const menuListStyle = computed(
  (): CSSProperties => ({
    padding: `${props.menuPadding}px`
  })
)

// 计算菜单项样式
const menuItemStyle = computed(
  (): CSSProperties => ({
    height: `${props.itemHeight}px`,
    padding: `0 ${props.itemPaddingX}px`,
    borderRadius: '4px'
  })
)

// 计算子菜单列表样式
const submenuListStyle = computed(
  (): CSSProperties => ({
    minWidth: 'max-content',
    padding: `${props.menuPadding}px 0`,
    borderRadius: `${props.borderRadius}px`
  })
)

// 计算菜单高度（用于边界检测）
const calculateMenuHeight = (): number => {
  let totalHeight = props.menuPadding * 2 // 上下内边距

  props.menuItems.forEach((item) => {
    totalHeight += props.itemHeight
    if (item.showLine) {
      totalHeight += 10 // 分割线额外高度
    }
  })

  return totalHeight
}

const show = (e: MouseEvent) => {
  e.preventDefault()
  e.stopPropagation()

  const screenWidth = window.innerWidth
  const screenHeight = window.innerHeight
  const menuHeight = calculateMenuHeight()

  // 计算最佳位置
  let x = e.clientX
  let y = e.clientY

  // 检查右边界
  if (x + props.menuWidth > screenWidth - props.boundaryDistance) {
    x = screenWidth - props.menuWidth - props.boundaryDistance
  }

  // 检查下边界
  if (y + menuHeight > screenHeight - props.boundaryDistance) {
    y = screenHeight - menuHeight - props.boundaryDistance
  }

  // 确保不会超出左边界和上边界
  x = Math.max(props.boundaryDistance, x)
  y = Math.max(props.boundaryDistance, y)

  position.value = {x, y}
  visible.value = true

  emit('show')

  // 延迟添加事件监听器，避免立即触发关闭
  setTimeout(() => {
    if (visible.value) {
      document.addEventListener('click', hide, {once: true})
      document.addEventListener('contextmenu', hide, {once: true})
    }
  }, 100)
}

const hide = () => {
  if (visible.value) {
    visible.value = false
    emit('hide')
    // 清理事件监听器
    document.removeEventListener('click', hide)
    document.removeEventListener('contextmenu', hide)
  }
}

const handleMenuClick = (item: MenuItemType) => {
  if (item.disabled) return
  emit('select', item)
  hide()
}

// 动画钩子函数
const onBeforeEnter = (el: Element) => {
  const element = el as HTMLElement
  element.style.transformOrigin = 'top left'
}

const onAfterLeave = () => {
  // 清理逻辑
  document.removeEventListener('click', hide)
  document.removeEventListener('contextmenu', hide)
}

// 导出方法供父组件调用
defineExpose({
  show,
  hide,
  visible: computed(() => visible.value)
})
</script>

<template>
  <!-- 右键菜单 -->
  <div class="menu-right">
    <Transition name="context-menu" @before-enter="onBeforeEnter" @after-leave="onAfterLeave">
      <div v-show="visible" :style="menuStyle" class="context-menu">
        <ul class="menu-list" :style="menuListStyle">
          <template v-for="item in menuItems" :key="item.key">
            <!-- 普通菜单项 -->
            <li
              v-if="!item.children"
              class="menu-item"
              :class="{ 'is-disabled': item.disabled, 'has-line': item.showLine }"
              :style="menuItemStyle"
              @click="handleMenuClick(item)"
            >
              <i v-if="item.icon" class="iconfont-sys" v-html="item.icon"></i>
              <span class="menu-label">{{ item.label }}</span>
            </li>

            <!-- 子菜单 -->
            <li v-else class="menu-item submenu" :style="menuItemStyle">
              <div class="submenu-title">
                <i v-if="item.icon" class="iconfont-sys" v-html="item.icon"></i>
                <span class="menu-label">{{ item.label }}</span>
                <i class="iconfont-sys submenu-arrow">&#xe865;</i>
              </div>
              <ul class="submenu-list" :style="submenuListStyle">
                <li
                  v-for="child in item.children"
                  :key="child.key"
                  class="menu-item"
                  :class="{ 'is-disabled': child.disabled, 'has-line': child.showLine }"
                  :style="menuItemStyle"
                  @click="handleMenuClick(child)"
                >
                  <i v-if="child.icon" class="iconfont-sys" v-html="child.icon"></i>
                  <span class="menu-label">{{ child.label }}</span>
                </li>
              </ul>
            </li>
          </template>
        </ul>
      </div>
    </Transition>
  </div>
</template>

<style scoped lang="scss">
.menu-right {
  .context-menu {
    width: v-bind('props.menuWidth + "px"');
    min-width: v-bind('props.menuWidth + "px"');
    background: var(--el-bg-color);
    border: 1px solid var(--el-border-color-light);
    border-radius: v-bind('props.borderRadius + "px"');
    box-shadow: var(--el-box-shadow-light);

    .menu-list {
      margin: 0;
      list-style: none;

      .menu-item {
        position: relative;
        display: flex;
        align-items: center;
        font-size: 13px;
        color: var(--el-text-color-primary);
        cursor: pointer;
        user-select: none;
        transition: background-color 0.15s ease;

        &:hover:not(.is-disabled) {
          background-color: rgba(var(--art-gray-200-rgb), 0.7);
        }

        &.has-line {
          margin-bottom: 10px;

          &::after {
            position: absolute;
            right: 0;
            bottom: -5px;
            left: 0;
            height: 1px;
            content: '';
            background-color: rgba(var(--art-gray-300-rgb), 0.5);
          }
        }

        i:not(.submenu-arrow) {
          flex-shrink: 0;
          margin-right: 8px;
          font-size: 16px;
          color: var(--art-gray-800);
        }

        .menu-label {
          flex: 1;
          overflow: hidden;
          color: var(--art-gray-800);
          text-overflow: ellipsis;
          white-space: nowrap;
        }

        &.is-disabled {
          color: var(--el-text-color-disabled);
          cursor: not-allowed;

          &:hover {
            background-color: transparent !important;
          }

          i:not(.submenu-arrow) {
            color: var(--el-text-color-disabled) !important;
          }

          .menu-label {
            color: var(--el-text-color-disabled) !important;
          }
        }

        &.submenu {
          &:hover {
            .submenu-list {
              display: block;
            }
          }

          .submenu-title {
            display: flex;
            align-items: center;
            width: 100%;

            .submenu-arrow {
              margin-right: 0;
              margin-left: auto;
              font-size: 12px;
              color: var(--art-gray-600);
              transition: transform 0.15s ease;
            }
          }

          &:hover .submenu-title .submenu-arrow {
            transform: rotate(90deg);
          }

          .submenu-list {
            position: absolute;
            top: 0;
            left: 100%;
            z-index: 2001;
            display: none;
            width: max-content;
            min-width: max-content;
            list-style: none;
            background: var(--el-bg-color);
            border: 1px solid var(--el-border-color-light);
            box-shadow: var(--el-box-shadow-light);

            .menu-item {
              position: relative;
              display: flex;
              align-items: center;
              margin: 0 6px;
              font-size: 13px;
              color: var(--el-text-color-primary);
              cursor: pointer;
              user-select: none;
              transition: background-color 0.15s ease;

              &:hover:not(.is-disabled) {
                background-color: rgba(var(--art-gray-200-rgb), 0.7);
              }

              &.has-line {
                margin-bottom: 10px;

                &::after {
                  position: absolute;
                  right: 0;
                  bottom: -5px;
                  left: 0;
                  height: 1px;
                  content: '';
                  background-color: rgba(var(--art-gray-300-rgb), 0.5);
                }
              }

              i:not(.submenu-arrow) {
                flex-shrink: 0;
                margin-right: 8px;
                font-size: 16px;
                color: var(--art-gray-800);
              }

              .menu-label {
                flex: 1;
                overflow: hidden;
                color: var(--art-gray-800);
                text-overflow: ellipsis;
                white-space: nowrap;
              }

              &.is-disabled {
                color: var(--el-text-color-disabled);
                cursor: not-allowed;

                &:hover {
                  background-color: transparent !important;
                }

                i:not(.submenu-arrow) {
                  color: var(--el-text-color-disabled) !important;
                }

                .menu-label {
                  color: var(--el-text-color-disabled) !important;
                }
              }
            }
          }
        }
      }
    }
  }

  // 动画样式
  .context-menu-enter-active,
  .context-menu-leave-active {
    transition: all v-bind('props.animationDuration + "ms"') ease-out;
  }

  .context-menu-enter-from,
  .context-menu-leave-to {
    opacity: 0;
    transform: scale(0.9);
  }

  .context-menu-enter-to,
  .context-menu-leave-from {
    opacity: 1;
    transform: scale(1);
  }
}
</style>
